﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using ZedGraph;

using System.IO;
using Microsoft.Win32;
using System.Collections.ObjectModel;
using System.Text.RegularExpressions;

namespace Lung_Sounds_V2
{
    /// <summary>
    /// Interaction logic for AverageWindow.xaml
    /// </summary>
    public partial class GraphWindow : Window
    {
        //List<List<double[]>> _snippets;
        double[][] _data;
        string[] _labels;

        double _xMulti;
        double _yMulti;

        public GraphPane GraphPane { get { return this.graphArea.GraphPane; } }

        private static Regex _csvSplitter = new Regex(@",(?=(?:[^""]*""[^""]*"")*(?![^""]*""))");

        public GraphWindow(string[] filenames)
        {
            HashSet<string> prefixes = new HashSet<string>();

            Dictionary<string, double[]> data = new Dictionary<string, double[]>();
            string title = "Average FFT";
            string xLabel = "Frequency (Hz)";
            string yLabel = "Amplitude (dB)";
            double xMulti = 1;
            double yMulti = 1;

            foreach (string fileName in filenames)
            {
                using (FileStream csvFile = File.Open(fileName, FileMode.Open))
                {
                    using (StreamReader csvReader = new StreamReader(csvFile))
                    {
                        string[] verline = csvReader.ReadLine().Split(',');

                        string labelPrefix = "";//System.IO.Path.GetFileNameWithoutExtension(fileName);

                        if (verline.Length > 0 && verline[0] == "Version_2")
                        {
                            title = (string)_csvSplitter.Split(csvReader.ReadLine()).GetValue(1);
                            xMulti = double.Parse((string)csvReader.ReadLine().Split(',').GetValue(1));
                            yMulti = double.Parse((string)csvReader.ReadLine().Split(',').GetValue(1));
                            yLabel = (string)_csvSplitter.Split(csvReader.ReadLine()).GetValue(1);
                            xLabel = (string)_csvSplitter.Split(csvReader.ReadLine()).GetValue(0);

                            int j = 0;
                            while (!csvReader.EndOfStream)
                            {
                                string[] currentRowString = csvReader.ReadLine().Split(','); 
                                double[] currentRow = new double[currentRowString.Length - 1];

                                int i = -1;
                                foreach (string valString in currentRowString)
                                {
                                    if (i >= 0)
                                    {
                                        double val;
                                        if (double.TryParse(valString, out val))
                                        {
                                            currentRow[i] = val;
                                        }
                                    }

                                    i++;
                                }

                                int dupeCount = 0;
                                while (prefixes.Contains(string.Format("{0}{1}", labelPrefix, dupeCount)))
                                {
                                    dupeCount++;
                                }
                                prefixes.Add(string.Format("{0}{1}", labelPrefix, dupeCount == 0 ? "" : dupeCount.ToString()));


                                data.Add(string.Format("{0}{1}{2}", labelPrefix, dupeCount == 0 ? "" : dupeCount.ToString(), Unescape(currentRowString[0])), currentRow);
                                j++;
                            }
                        }
                        else
                        {
                            csvReader.BaseStream.Seek(0, SeekOrigin.Begin);
                            csvReader.DiscardBufferedData();
                            LoadLegacyFormat(csvReader, labelPrefix, prefixes, data);
                        }
                    }
                }
            }

            init(data, title, xLabel, yLabel, xMulti, yMulti);
        }

        public GraphWindow(Dictionary<string, double[]> data, string title, string xLabel, string yLabel, double xMulti = 1, double yMulti = 1)
        {
            init(data, title, xLabel, yLabel, xMulti, yMulti);
        }

        public GraphWindow(Dictionary<string, List<double[]>> snippets, string title, string xLabel, string yLabel, double xMulti = 1, double yMulti = 1)
        {
            Dictionary<string, double[]> data = CalcAverage(snippets);

            init(data, title, xLabel, yLabel, xMulti, yMulti);
        }

        private void init(Dictionary<string, double[]> data, string title, string xLabel, string yLabel, double xMulti, double yMulti)
        {
            _labels = data.Keys.ToArray<string>();
            _data = data.Values.ToArray();
            _xMulti = xMulti;
            _yMulti = yMulti;

            InitializeComponent();

            this.GraphPane.XAxis.Scale.Min = 100;
            this.GraphPane.XAxis.Scale.Max = 2000;
            this.GraphPane.XAxis.Title.Text = xLabel;
            this.GraphPane.XAxis.Title.FontSpec.Size = 10;

            this.GraphPane.YAxis.Title.Text = yLabel;
            this.GraphPane.YAxis.Title.FontSpec.Size = 10;

            this.GraphPane.Title.Text = title;
            this.GraphPane.Title.FontSpec.Size = 12;

            this.GraphPane.YAxis.Scale.FontSpec.Size = 10;
            this.GraphPane.YAxis.Scale.MinorStep = 1;

            this.GraphPane.XAxis.Scale.FontSpec.Size = 10;
            this.GraphPane.XAxis.Scale.MinorStep = 100;

            this.GraphPane.Legend.Position = LegendPos.Right;

            this.GraphPane.IsFontsScaled = false;
        }

        private void LoadLegacyFormat(StreamReader csvReader, string labelPrefix, HashSet<string> prefixes, Dictionary<string, double[]> snippetAvg)
        {
            double windowSize = 0;

            double tmpWindowSize;
            if (double.TryParse(csvReader.ReadLine(), out tmpWindowSize))
            {
                windowSize = tmpWindowSize;

                int j = 0;
                while (!csvReader.EndOfStream)
                {
                    string[] currentRowString = csvReader.ReadLine().Split(',');
                    double[] currentRow = new double[currentRowString.Length];

                    int i = -1;
                    foreach (string freqString in currentRowString)
                    {
                        if (i >= 0)
                        {
                            double freq;
                            if (double.TryParse(freqString, out freq))
                            {
                                currentRow[i] = freq;
                            }
                        }

                        i++;
                    }

                    int dupeCount = 0;
                    while (prefixes.Contains(string.Format("{0}{1}", labelPrefix, dupeCount)))
                    {
                        dupeCount++;
                    }
                    prefixes.Add(string.Format("{0}{1}", labelPrefix, dupeCount == 0 ? "" : dupeCount.ToString()));


                    snippetAvg.Add(string.Format("{0}{1}-{2}", labelPrefix, dupeCount == 0 ? "" : dupeCount.ToString(), j), currentRow);
                    j++;
                }
            }
            else
            {
                MessageBox.Show("Incorrect data file format :(, no window size found", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        private void Window_Loaded_1(object sender, RoutedEventArgs e)
        {
            FillGraph(_data);
        }

        private void menuItemSave_Click(object sender, RoutedEventArgs e)
        {
            // Save a csv of the averages, yay!

            SaveFileDialog saveDialog = new SaveFileDialog();

            saveDialog.Filter = "Comma delimited file (*.CSV)|*.CSV";

            if (saveDialog.ShowDialog() == true)
            {
                using (FileStream csvStream = File.Open(saveDialog.FileName, FileMode.Create))
                {
                    using (StreamWriter csvWriter = new StreamWriter(csvStream))
                    {
                        // [version] [title] [xmulti] [ymulti]
                        csvWriter.WriteLine("Version_2");
                        csvWriter.WriteLine(string.Format("Title,{0}", this.GraphPane.Title.Text));
                        csvWriter.WriteLine(string.Format("xmulti,{0}", _xMulti));
                        csvWriter.WriteLine(string.Format("ymulti,{0}", _yMulti));
                        csvWriter.WriteLine(string.Format("ylabel,{0}", this.GraphPane.YAxis.Title.Text));

                        // Then x values
                        csvWriter.Write(string.Format("{0},", this.GraphPane.XAxis.Title.Text));
                        for (int i = 0; i < _data[0].Length; i++)
                        {
                            csvWriter.Write(i);
                            csvWriter.Write(',');
                        }

                        csvWriter.WriteLine();

                        // Then y values
                        for (int i = 0; i < _data.Length; i++)
                        {
                            csvWriter.Write(string.Format("{0},", Escape(_labels[i])));

                            foreach (double val in _data[i])
                            {
                                csvWriter.Write(val);
                                csvWriter.Write(',');
                            }
                            csvWriter.WriteLine();
                        }
                    }
                }
                FillGraph(_data);
            }
        }

        private Dictionary<string, double[]> CalcAverage(Dictionary<string, List<double[]>> snippets)
        {

            Dictionary<string, double[]> snippetAvg = new Dictionary<string, double[]>(snippets.Count);

            foreach (KeyValuePair<string, List<double[]>> snippetKvp in snippets)
            {
                List<double[]> snippet = snippetKvp.Value;
                List<double> sumWindow = new List<double>();

                foreach (double[] window in snippet)
                {
                    int j = 0;

                    foreach (double freq in window)
                    {
                        if (sumWindow.Count <= j)
                        {
                            sumWindow.Add(freq);
                        }
                        else
                        {
                            sumWindow[j] += freq;
                        }

                        j++;
                    }
                }

                double[] avgWindow = new double[sumWindow.Count];

                for (int i = 0; i < sumWindow.Count; i++)
                {
                    avgWindow[i] = sumWindow[i] / snippet.Count;
                }

                snippetAvg.Add(snippetKvp.Key, avgWindow);
            }

            return snippetAvg;
        }

        private void FillGraph(double[][] snippetAvg)
        {
            // average Graph
            GraphPane graphPane = graphArea.GraphPane;

            graphPane.CurveList.Clear();

            for (int i = 0; i < snippetAvg.Length; i++)
            {
                double[] avgWindow = snippetAvg[i];

                //Random randonGen = new Random(int.Parse(Guid.NewGuid().ToString().Substring(0, 8), System.Globalization.NumberStyles.HexNumber));
                //System.Drawing.Color randomColor = System.Drawing.Color.FromArgb(255, (byte)randonGen.Next(255), (byte)randonGen.Next(255),
                //(byte)randonGen.Next(255));

                System.Drawing.Color randomColor;
                if (i >= KellysMaxContrastSet.Count)
                {
                    Random randonGen = new Random(int.Parse(Guid.NewGuid().ToString().Substring(0, 8), System.Globalization.NumberStyles.HexNumber));
                    randomColor = System.Drawing.Color.FromArgb(255, (byte)randonGen.Next(255), (byte)randonGen.Next(255),
                    (byte)randonGen.Next(255));
                }
                else
                {
                    randomColor = KellysMaxContrastSet[i];
                }

                PointPairList fftList = new PointPairList();

                string seriesLabel;

                if (_labels != null && _labels.Length > i)
                {
                    seriesLabel = _labels[i];
                }
                else
                {
                    seriesLabel = string.Format("Series {0}", i);
                }
                
                LineItem seriesCurve = graphPane.AddCurve(seriesLabel,
                    fftList, randomColor, SymbolType.None);

                seriesCurve.Label.IsVisible = false;

                int x = 0;
                foreach (double freq in avgWindow)
                {
                    seriesCurve.AddPoint(x * _xMulti, freq * _yMulti);
                    x++;
                }
            }

            graphArea.AxisChange();
            graphArea.Invalidate();

            listViewCurves.Items.Refresh();
        }


        private void CheckBox_Checked_1(object sender, RoutedEventArgs e)
        {
            graphArea.Invalidate();
        }

        private void CheckBox_Unchecked_1(object sender, RoutedEventArgs e)
        {
            graphArea.Invalidate();
        }

        private void menuItemSaveImage_Click(object sender, RoutedEventArgs e)
        {
            foreach (CurveItem item in graphArea.GraphPane.CurveList)
            {
                if (item.IsVisible)
                {
                    item.Label.IsVisible = true;
                }
            }

            int oldWidth = this.graphArea.Width;
            int oldHeight = this.graphArea.Height;

            this.graphArea.Dock = System.Windows.Forms.DockStyle.None;
            this.graphArea.Width = 650;
            this.graphArea.Height = 470;

            this.GraphPane.XAxis.Title.FontSpec.Size = 18;
            this.GraphPane.YAxis.Title.FontSpec.Size = 18;
            this.GraphPane.XAxis.Title.FontSpec.IsBold = false;
            this.GraphPane.YAxis.Title.FontSpec.IsBold = false;
            this.GraphPane.Title.IsVisible = false;
            this.GraphPane.XAxis.Scale.FontSpec.Size = 14;
            this.GraphPane.YAxis.Scale.FontSpec.Size = 14;
            this.GraphPane.Border.IsVisible = false;

            //this.GraphPane.YAxis.Scale.Max = -70;
            //this.GraphPane.YAxis.Scale.Min = -127;

            graphArea.Invalidate();

            graphArea.SaveAs();

            this.graphArea.Width = oldWidth;
            this.graphArea.Height = oldHeight;
            this.graphArea.Dock = System.Windows.Forms.DockStyle.Fill;
            this.GraphPane.XAxis.Title.FontSpec.Size = 10;
            this.GraphPane.YAxis.Title.FontSpec.Size = 10;
            this.GraphPane.XAxis.Title.FontSpec.IsBold = true;
            this.GraphPane.YAxis.Title.FontSpec.IsBold = true;
            this.GraphPane.Title.IsVisible = true;
            this.GraphPane.XAxis.Scale.FontSpec.Size = 8;
            this.GraphPane.YAxis.Scale.FontSpec.Size = 8;
            this.GraphPane.Border.IsVisible = true;

            foreach (CurveItem item in graphArea.GraphPane.CurveList)
            {
                item.Label.IsVisible = false;
            }
            graphArea.Invalidate();
        }

        private void menuItemRefresh_Click(object sender, RoutedEventArgs e)
        {
            FillGraph(_data);
        }

        public static ReadOnlyCollection<System.Drawing.Color> KellysMaxContrastSet
        {
            get { return _kellysMaxContrastSet.AsReadOnly(); }
        }
        private static readonly List<System.Drawing.Color> _kellysMaxContrastSet = new List<System.Drawing.Color>
        {
            UIntToColor(0xFFFFB300), //Vivid Yellow
            UIntToColor(0xFF803E75), //Strong Purple
            UIntToColor(0xFFFF6800), //Vivid Orange
            UIntToColor(0xFFA6BDD7), //Very Light Blue
            UIntToColor(0xFFC10020), //Vivid Red
            UIntToColor(0xFFCEA262), //Grayish Yellow
            UIntToColor(0xFF817066), //Medium Gray

            //The following will not be good for people with defective color vision
            UIntToColor(0xFF007D34), //Vivid Green
            UIntToColor(0xFFF6768E), //Strong Purplish Pink
            UIntToColor(0xFF00538A), //Strong Blue
            UIntToColor(0xFFFF7A5C), //Strong Yellowish Pink
            UIntToColor(0xFF53377A), //Strong Violet
            UIntToColor(0xFFFF8E00), //Vivid Orange Yellow
            UIntToColor(0xFFB32851), //Strong Purplish Red
            UIntToColor(0xFFF4C800), //Vivid Greenish Yellow
            UIntToColor(0xFF7F180D), //Strong Reddish Brown
            UIntToColor(0xFF93AA00), //Vivid Yellowish Green
            UIntToColor(0xFF593315), //Deep Yellowish Brown
            UIntToColor(0xFFF13A13), //Vivid Reddish Orange
            UIntToColor(0xFF232C16), //Dark Olive Green
        };

        public static ReadOnlyCollection<System.Drawing.Color> BoyntonOptimized
        {
            get { return _boyntonOptimized.AsReadOnly(); }
        }
        private static readonly List<System.Drawing.Color> _boyntonOptimized = new List<System.Drawing.Color>
        {
            System.Drawing.Color.FromArgb(0, 0, 255),      //Blue
            System.Drawing.Color.FromArgb(255, 0, 0),      //Red
            System.Drawing.Color.FromArgb(0, 255, 0),      //Green
            System.Drawing.Color.FromArgb(255, 255, 0),    //Yellow
            System.Drawing.Color.FromArgb(255, 0, 255),    //Magenta
            System.Drawing.Color.FromArgb(255, 128, 128),  //Pink
            System.Drawing.Color.FromArgb(128, 128, 128),  //Gray
            System.Drawing.Color.FromArgb(128, 0, 0),      //Brown
            System.Drawing.Color.FromArgb(255, 128, 0),    //Orange
       };

        static public System.Drawing.Color UIntToColor(uint color)
        {
            var a = (byte)(color >> 24);
            var r = (byte)(color >> 16);
            var g = (byte)(color >> 8);
            var b = (byte)(color >> 0);
            return System.Drawing.Color.FromArgb(a, r, g, b);
        }

        public static string Escape(string s)
        {
            if (s.Contains(QUOTE))
                s = s.Replace(QUOTE, ESCAPED_QUOTE);

            if (s.IndexOfAny(CHARACTERS_THAT_MUST_BE_QUOTED) > -1)
                s = QUOTE + s + QUOTE;

            return s;
        }

        public static string Unescape(string s)
        {
            if (s.StartsWith(QUOTE) && s.EndsWith(QUOTE))
            {
                s = s.Substring(1, s.Length - 2);

                if (s.Contains(ESCAPED_QUOTE))
                    s = s.Replace(ESCAPED_QUOTE, QUOTE);
            }

            return s;
        }


        private const string QUOTE = "\"";
        private const string ESCAPED_QUOTE = "\"\"";
        private static char[] CHARACTERS_THAT_MUST_BE_QUOTED = { ',', '"', '\n' };

        private void menuItemSelectAll_Click(object sender, RoutedEventArgs e)
        {
            foreach (var curve in GraphPane.CurveList)
            {
                curve.IsVisible = true;
            }

            graphArea.Invalidate();

            listViewCurves.ItemsSource = null;
            listViewCurves.ItemsSource = GraphPane.CurveList;
        }

        private void menuItemDeselectAll_Click(object sender, RoutedEventArgs e)
        {
            foreach (var curve in GraphPane.CurveList)
            {
                curve.IsVisible = false;
            }

            graphArea.Invalidate();

            listViewCurves.ItemsSource = null;
            listViewCurves.ItemsSource = GraphPane.CurveList;
        }
    }

    [ValueConversion(typeof(Color), typeof(Brush))]
    public class ColorToBrushConverter : IValueConverter
    {
        object IValueConverter.Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            System.Drawing.Color color = (System.Drawing.Color)value;

            return new SolidColorBrush(Color.FromArgb(color.A, color.R, color.G, color.B));
        }


        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            return ((SolidColorBrush)value).Color;
        }
    }
}
